#include "./flash.h"
#include "../device/compat.h"

namespace driver::flash {

static void DELAY(volatile uint32_t x) {
	x *= 1000;
	while (x --);
} 

constexpr uint32_t FlashKey1 = 0x45670123;
constexpr uint32_t FlashKey2 = 0xCDEF89AB;

static void UnLock() {
    FLASH->KEYR = FlashKey1;
    FLASH->KEYR = FlashKey2;
}

static void Lock() {
    FLASH->CR |= 1 << 7;
}

enum FlashError {
	OK = 0x00,
	BUY = 0x01,
	PARAMS_ERROR = 0x02,
	WRITE_PROTECTED = 0x03,
	TIME_OUT = 0x04,
};

static FlashError GetStatus() {
	uint32_t ret = FLASH->SR;
	if (ret & (0x01 << 0)) return BUY;
	if (ret & (0x01 << 2)) return PARAMS_ERROR;
	if (ret & (0x01 << 4)) return WRITE_PROTECTED;
	return OK;
}

static FlashError WaitComplete(int32_t timeOut) {
	auto ret = OK;
	do {
		ret = GetStatus();
		if (ret != BUY) {
			break;
		}
		DELAY(1);
		timeOut --;
	} while (timeOut > 0);
	if (timeOut <= 0) { 
		return TIME_OUT;
	}
	return ret;
}

static FlashError FlashWrite16Bit(uint32_t address, uint16_t data) {
	auto ret = WaitComplete(0xFF);
	if (ret == OK) {
		FLASH->CR |= 0x01;
		*((volatile uint16_t *)address) = data;
		ret = WaitComplete(0xFF);
		if (ret != BUY) {
			FLASH->CR &= ~0x01;
		}
	}
	if (ret == OK) {
		if (data != *((volatile uint16_t *)address)) {
			return PARAMS_ERROR;
		}
	}
	return ret;
}

bool Write(uint32_t addr, const void *buf, uint32_t size) {
	UnLock();
	auto ret = OK;
	auto p = reinterpret_cast<const uint16_t *>(buf);
	size = (size + 1) / 2;
	for (int32_t i = 0; i < size; i ++) {
		ret = FlashWrite16Bit(addr, p[i]);
		if (ret != OK) {
			Lock();
			return ret;
		}
		addr += 2;
	}
	Lock();
	return ret == OK;
}

bool Erase(uint32_t addr) {
	UnLock();
	auto ret = WaitComplete(1000);
	if (ret == OK) {
		FLASH->CR |= 1 << 1;
		FLASH->AR = addr;
		FLASH->CR |= 1 << 6;
		ret = WaitComplete(1000);
		if (ret != BUY) {
			FLASH->CR &= ~(1 << 1);
		}
	}
	Lock();
	return ret == OK;
}

bool Erase(uint32_t addr, uint32_t size) {
	uint32_t index = 0;
	for (;index < size;) {
		if (!Erase(addr + index)) {
			return false;
		}
		index += SectorSize;
	}
	return true;
}

bool Move(uint32_t target, uint32_t src, uint32_t size) {
	if (!Erase(target, size)) {
		return false;
	}
	return Write(target, reinterpret_cast<const void *>(src), size);
}

void Read(uint32_t addr, void *buf, uint32_t size) {
	auto p = static_cast<uint16_t *>(buf);
	auto a = reinterpret_cast<const uint16_t *>(addr);
	size /= 2;
	for (int32_t i = 0; i < size; i ++) {
		p[i] = a[i]; 
	}
}

}